package com.kstudio.videocapture.gl;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.LinkedList;

import javax.microedition.khronos.opengles.GL10;

import android.graphics.Bitmap;

public final class LinearListGL2D {

    private FloatBuffer mTextureBuffer;
    private ByteBuffer  mIndexBuffer;
    private int mIndiceslength;

    private int   mViewHeight;

    private float mHeight;
    private float mWidth;
    private float mLeft;
    private float mTop;
    private float mRight;
    private float mBottom;

    private float mTranslateX;
    private float mTranslateY;

    private float mScaleX;
    private float mScaleY;

    private float   mScrollXStart;
    private float   mScrollXPos;
    private boolean mScrollInvalidate;

    private LinkedList<DrawItem> mDrawItem;
    private LinkedList<DrawItem> mDrawItemCashe;
    private int mDrawGapPadding;
    private int mDrawItemWidth;
    private int mDrawItemHeight;

    private ArrayList<String> mListData;

    private class DrawItem {
        static final int FLAG_TEXTURE_NONE        = 0x0000;
        static final int FLAG_TEXTURE_INIT        = 0x0001;
        static final int FLAG_TEXTURE_NEED        = 0x0002;
        static final int FLAG_TEXTURE_LOADED      = 0x0004;

        FloatBuffer vertexBuffer;

        int[] texture;
        int textureId;
        int flagTexture;

        float left;
        float right;

        DrawItem() {
            flagTexture = FLAG_TEXTURE_NONE;
            textureId = 0;
            texture = new int[1];
        }
    }

    private LinearListGL2D(GL10 gl, ArrayList<String> listData, int viewheight) {
        mHeight = 0;;
        mWidth = 0;

        mLeft = 0;
        mTop = 0;
        mRight = 0;
        mBottom = 0;

        mTranslateX = 0;
        mTranslateY = 0;

        mScaleX = 1f;
        mScaleY = 1f;

        mScrollInvalidate = false;

        mViewHeight = viewheight;

        mListData = listData;

        mDrawItem = new LinkedList<DrawItem>();
        mDrawItemCashe = new LinkedList<DrawItem>();
    }

    public static LinearListGL2D create(GL10 gl, ArrayList<String> listData, int viewheight) {
        LinearListGL2D draw2D = new LinearListGL2D(gl, listData, viewheight);

        return draw2D;
    }

    public void draw(GL10 gl) {
        doScroll();

        gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glLoadIdentity();
        gl.glPushMatrix();

        gl.glScalef(mScaleX, mScaleY, 1f);
        gl.glTranslatef(mTranslateX, mTranslateY, -1f);

        gl.glMatrixMode(GL10.GL_TEXTURE);
        gl.glLoadIdentity();

        gl.glFrontFace(GL10.GL_CCW);
        gl.glEnable(GL10.GL_CULL_FACE);
        gl.glCullFace(GL10.GL_BACK);

        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

        for(DrawItem item : mDrawItem) {
            if(item.flagTexture == DrawItem.FLAG_TEXTURE_NONE ||
              (item.flagTexture & DrawItem.FLAG_TEXTURE_NEED) == DrawItem.FLAG_TEXTURE_NEED) {
//                if(!loadTexture(gl, item, mListData.get(item.textureId).getBitmap())) {
//                    continue;
//                }
            } else if((item.flagTexture & DrawItem.FLAG_TEXTURE_LOADED) == DrawItem.FLAG_TEXTURE_LOADED){
                gl.glBindTexture(GL10.GL_TEXTURE_2D, item.texture[0]);
            } else {
                continue;
            }
            gl.glVertexPointer(2, GL10.GL_FLOAT, 0, item.vertexBuffer);
            gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTextureBuffer);

            gl.glDrawElements(GL10.GL_TRIANGLES, mIndiceslength, GL10.GL_UNSIGNED_BYTE, mIndexBuffer);
        }

        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
        gl.glDisable(GL10.GL_CULL_FACE);
        // Draw

        gl.glPopMatrix();
        gl.glLoadIdentity();
    }

    public void move(int x, int y) {
        mTranslateX = x;
        mTranslateY = y;
    }

    public void move(float x, float y) {
        mTranslateX = x;
        mTranslateY = y;
    }

    public void scale(float x, float y) {
        mScaleX = x;
        mScaleY = y;
    }

    public void startScrollX(float x) {
        mScrollXStart = x;
    }

    public void scrollX(float x) {
        mScrollXPos  = x;
        mScrollInvalidate = true;
    }

    public boolean checkInsideFromView(float x, float y) {
        float glX = (float) x / mScaleX - mTranslateX;
        float glY = ((mViewHeight - (float) y) / mScaleY) - mTranslateY;

        if(glX >= getLeft() && glX <= getRight() &&
           glY >= getBottom() && glY <= getTop()) {
            return true;
        }
        return false;
    }

    public float getHeight() { return mHeight; }
    public float getWidth()  { return mWidth; }

    public float getLeft() { return mLeft + mTranslateX; }
    public float getRight() { return mRight + mTranslateX; }
    public float getTop() { return mTop + mTranslateY; }
    public float getBottom() { return mBottom + mTranslateY; }

    public float getTranslateX() { return mTranslateX; }
    public float getTranslateY() { return mTranslateY; }
    public float getScaleX() { return mScaleX; }
    public float getScaleY() { return mScaleY; }

    public void setAttribute(int itemWidth, int itemHeight, int itemPadding, int listCnt) {
        mDrawGapPadding = itemPadding;
        mDrawItemWidth = itemWidth;
        mDrawItemHeight = itemHeight;
    }

    public void setLayout(int left, int right, int bottom, int top) {
        mLeft   = left;
        mTop    = top;
        mRight  = right;
        mBottom = bottom;
        mHeight = top - bottom;
        mWidth  = right - left;

        float l = mLeft;
        float r = l + mDrawItemWidth;
        float b = mBottom;
        float t = b + mDrawItemHeight;

        int i = 0;
        while(l <= mWidth) {
            DrawItem item = createDrawItem(l, r, b, t, i);

            mDrawItem.addLast(item);

            l = r + mDrawGapPadding;
            r = l + mDrawItemWidth;

            i++;
        }

        float texture[] = {
            0.0f, 1.0f,
            1.0f, 1.0f,
            1.0f, 0.0f,
            0.0f, 0.0f,
        };

        byte indices[] = {
            0,1,2,
            0,2,3
        };

        mIndiceslength = indices.length;

        ByteBuffer byteBuf = ByteBuffer.allocateDirect(texture.length * 4);
        byteBuf.order(ByteOrder.nativeOrder());
        mTextureBuffer = byteBuf.asFloatBuffer();
        mTextureBuffer.put(texture);
        mTextureBuffer.position(0);

        mIndexBuffer = ByteBuffer.allocateDirect(indices.length);
        mIndexBuffer.put(indices);
        mIndexBuffer.position(0);
    }

    /* Private Function */
    private boolean loadTexture(GL10 gl, DrawItem item, Bitmap bm) {
        if(bm == null) {
            return false;
        }

        if((item.flagTexture & DrawItem.FLAG_TEXTURE_INIT) != DrawItem.FLAG_TEXTURE_INIT) {
            gl.glGenTextures(1, item.texture, 0);
            item.flagTexture |= DrawItem.FLAG_TEXTURE_INIT;
        }

        gl.glBindTexture(GL10.GL_TEXTURE_2D, item.texture[0]);

        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);

        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);

//        GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, mListData.get(item.textureId).getBitmap(), 0);

        item.flagTexture ^= DrawItem.FLAG_TEXTURE_NEED;
        item.flagTexture |= DrawItem.FLAG_TEXTURE_LOADED;

        return true;
    }

    private void doScroll() {
        if(mScrollInvalidate) {
            float l;
            float r;
            float b = mBottom;
            float t = b + mDrawItemHeight;

            float scroll = mScrollXPos - mScrollXStart;
            int removeCnt = 0;

            DrawItem item;

            if(scroll < 0) {
                removeCnt = 0;

                item = mDrawItem.getLast();

                if(item.textureId >= mListData.size() - 1) {
                    l = item.left + scroll;
                    if(l < mRight - mDrawItemWidth) {
                        scroll = (mRight - item.left) - mDrawItemWidth;
                    }
                }
                if(scroll == 0) return;

                for(int i = 0; i < mDrawItem.size(); i++) {
                    item = mDrawItem.get(i);

                    l = item.left + scroll;
                    if(l < mLeft) l = mLeft;

                    if(l > mLeft) r = l + mDrawItemWidth;
                    else          r = item.right + scroll;

                    if(r > mRight) r = mRight;

                    if(r < mLeft) {
                        removeCnt++;
                    } else {
                        changeDrawItem(item, l, r, b, t);
                    }
                }

                for(int i = 0; i < removeCnt; i++) {
                    removeDrawItem(mDrawItem.removeFirst());
                }

                item = mDrawItem.getLast();
                int textureId = item.textureId;

                while(item.textureId < mListData.size() - 1 && (item.right - item.left) == mDrawItemWidth) {
                    l = item.right + mDrawGapPadding;
                    if(l > mRight) break;
                    r = l + mDrawItemWidth;
                    if(r > mRight) r = mRight;

                    textureId++;

                    item = createDrawItem(l, r, b, t, textureId);

                    mDrawItem.addLast(item);
                }
            } else {
                removeCnt = mDrawItem.size();

                item = mDrawItem.getFirst();

                if(item.textureId == 0) {
                    r = item.right + scroll;
                    if(r > mLeft + mDrawItemWidth) {
                        scroll = (mLeft + mDrawItemWidth - item.right);
                    }
                }
                if(scroll == 0) { return; }

                for(int i = 0; i < mDrawItem.size(); i++) {
                    item = mDrawItem.get(i);

                    l = item.left + scroll;
                    r = item.right + scroll;
                    if(r < mLeft + mDrawItemWidth) {
                        l = mLeft;
                    } else if(r > mRight) {
                        r = mRight;
                    } else {
                        l = r - mDrawItemWidth;
                    }

                    if(l > mRight) {
                        removeCnt = i;
                        break;
                    } else {
                        changeDrawItem(item, l, r, b, t);
                    }
                }

                int size = mDrawItem.size();
                for(int i = removeCnt; i < size; i++) {
                    removeDrawItem(mDrawItem.removeLast());
                }

                item = mDrawItem.getFirst();
                int textureId = item.textureId;

                while(item.textureId > 0 && (item.right - item.left) == mDrawItemWidth) {
                    r = item.left - mDrawGapPadding;

                    if(r < mLeft) break;
                    l = r - mDrawItemWidth;
                    if(l < mLeft) l = mLeft;

                    textureId--;

                    item = createDrawItem(l, r, b, t, textureId);

                    mDrawItem.addFirst(item);
                }
            }

            mScrollXStart = mScrollXPos;
            mScrollInvalidate = false;
        }
    }

    private DrawItem createDrawItem(float l, float r, float b, float t, int textureId) {
        DrawItem item = null;
        float vertices[] = {l, b, r, b, r, t, l, t};

        if(mDrawItemCashe.size() <= 0) {
            item = new DrawItem();

            ByteBuffer byteBuf = ByteBuffer.allocateDirect(vertices.length * 4);
            byteBuf.order(ByteOrder.nativeOrder());

            item.vertexBuffer = byteBuf.asFloatBuffer();
            item.vertexBuffer.put(vertices);
            item.vertexBuffer.position(0);

            item.textureId = textureId;
            item.left  = l;
            item.right = r;
        } else {
            item = mDrawItemCashe.remove();

            item.vertexBuffer.clear();
            item.vertexBuffer.put(vertices);
            item.vertexBuffer.position(0);

            item.flagTexture |= DrawItem.FLAG_TEXTURE_NEED;
            item.textureId = textureId;
            item.left  = l;
            item.right = r;
        }

        return item;
    }

    private void changeDrawItem(DrawItem item, float l, float r, float b, float t) {
        float vertices[] = { l, b, r, b, r, t, l, t };

        item.vertexBuffer.clear();
        item.vertexBuffer.put(vertices);
        item.vertexBuffer.position(0);

        item.left = l;
        item.right = r;
    }

    private void removeDrawItem(DrawItem item) {
        mDrawItemCashe.add(item);
    }
}
